package com.xsjiong.learning.gym;

import com.sun.xml.internal.ws.wsdl.writer.document.soap.Body;
import com.xsjiong.learning.Environment;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

public class EatingSnake extends JFrame implements Environment, KeyListener {
	@Override
	public int MaxAction() {
		return 3;
	}

	@Override
	public double[] GetState() {
		return A;
	}

	public EatingSnake() {
		this("EatingSnake", 5, 5);
	}

	public EatingSnake(String title) {
		this(title, 5, 5);
	}

	public EatingSnake(int width, int height) {
		this("EatingSnake", width, height);
	}

	private final double[] A;
	public static final int SINGLE_SIZE = 100, BORDER = 1;

	public final int GameWidth, GameHeight;

	public EatingSnake(String title, int width, int height) {
		super(title);
		if (width < 4 || height < 4) throw new IllegalArgumentException("Width And Height Cannot Be Fewer Than 4");
		this.GameWidth = width;
		this.GameHeight = height;
		setSize(500, 500);
		setBackground(Color.WHITE);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(null);
		setContentPane(new JPanel(false) {
			@Override
			public void paint(Graphics g) {
				super.paint(g);
				int x = 0, y = 0;
				float sgw = (float) getWidth() / GameWidth;
				float sgh = (float) getHeight() / GameHeight;
				for (int cur = 0; cur < A.length; cur++) {
					if (cur == Food) {
						g.setColor(Color.YELLOW);
						g.fillRect((int) (sgw * x + BORDER), (int) (sgh * y + BORDER), (int) (sgw - (BORDER << 1)), (int) (sgh - (BORDER << 1)));
					} else if (A[cur] == 1) {
						g.setColor(Color.RED);
						g.fillRect((int) (sgw * x + BORDER), (int) (sgh * y + BORDER), (int) (sgw - (BORDER << 1)), (int) (sgh - (BORDER << 1)));
					}
					if (++x >= GameWidth) {
						x = 0;
						y++;
					}
				}
			}
		});
		addKeyListener(this);
		A = new double[width * height];
		SpawnSnake();
		SpawnFood();
	}

	private int GetIndex(int x, int y) {
		return y * GameWidth + x;
	}

	private static final Random R = new Random();
	private int Food;

	private void SpawnSnake() {
		int x = R.nextInt(GameHeight) * GameWidth + R.nextInt(Math.min(5, GameWidth - 1)) + 1;
		A[x] = A[x - 1] = 1;
		BodyParts.add(x - 1);
		BodyParts.add(x);
	}

	private void SpawnFood() {
		int cur = R.nextInt(GameWidth * GameHeight - BodyParts.size()) + 1;
		Food = -1;
		while (cur-- != 0)
			while (A[++Food] == 1) ;
	}

	private static final int[] MX = {1, 0, -1, 0}, MY = {0, 1, 0, -1};
	private int Direction = 0;

	private LinkedList<Integer> BodyParts = new LinkedList<>();

	@Override
	public int Apply(int action) {
		if (action == 1) {
			if (--Direction == -1) Direction = 3;
		} else if (action == 2) if (++Direction == 4) Direction = 0;
		int x;
		x = BodyParts.get(BodyParts.size() - 1);
		int y = x / GameWidth;
		x -= y * GameWidth;
		x += MX[Direction];
		y += MY[Direction];
		if (x == -1 || x == GameWidth || y == -1 || y == GameHeight || A[(x = GetIndex(x, y))] == 1) {
			System.out.println("Game Over!");
			return -100;
		}
		BodyParts.add(x);
		A[x] = 1;
		boolean ate = Food == x;
		if (ate) SpawnFood();
		else A[BodyParts.remove(0)] = 0;
		getContentPane().repaint();
		return ate ? 100 : 0;
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

	@Override
	public void keyPressed(KeyEvent e) {
	}

	private static final int[][] Turning = {
			{0, 2, 0, 1},
			{1, 0, 2, 0},
			{0, 1, 0, 2},
			{2, 0, 1, 0}
	};

	@Override
	public void keyReleased(KeyEvent e) {
		int x = -1;
		switch (e.getKeyCode()) {
			case KeyEvent.VK_RIGHT:
				x = 0;
				break;
			case KeyEvent.VK_DOWN:
				x = 1;
				break;
			case KeyEvent.VK_LEFT:
				x = 2;
				break;
			case KeyEvent.VK_UP:
				x = 3;
				break;
		}
		if (x != -1) Apply(Turning[Direction][x]);
	}
}
